<?php
/***
 * Candy框架 文件缓存类	
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2019-08-05 15:00:01 $   
 */
 
declare(strict_types=1);
namespace Candy\Extend\Cache\Driver;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');

final Class File {
    public $tip_message;		//提示信息
    protected $cacheDir;		//缓存目录
    private $cacheFileName;	//缓存文件名
    private $cacheFileSuffix;	//缓存文件后缀
	
    /**
     *@param $dir	String  设置缓存目录， C_cacheDir, 如果都没有设置创建一个缺省的缓存文件 
     *@param $cacheFileSuffix 缓存文件的后缀 默认使用.php, 建议使用这个后缀， 不能被访问。
     */  
    public function __construct(array $confing = [])
	{
		if(isset($confing['dir'])){
			$this->cacheDir = $confing['dir'];
		}elseif(!is_null(C('C_cacheDir'))){
			$this->cacheDir = C('C_cacheDir');
		}else {
			$this->cacheDir = RUNTIME.'Tmps';
		}
		
       	$this->cacheFileSuffix = isset($confing['ext']) ? $confing['ext'] : '.php';		
      	if(!$this->dirIsvalid($this->cacheDir)){
			die($this->tip_message);//创建目录失败
     	}
    }
    /**
     * 添加一个值， 如果已经存在，则返回false
     *
     * @param $cacheKey mixed	键值
     * @param $cache_value mixed 保存的缓存数据
     * @param $lifeTime int	保存时间，如果设置为0则永远不过期， 默认值为0
     * @return boolean 	设置成功为true, 失败为false
     *
     */
    public function add(mixed $cacheKey,mixed $cache_value,int $lifeTime=0): bool
	{
		if(file_exists($this->getCacheFileName($cacheKey))){
			$this->tip_message = '缓存数据已存在.';
			return false;
		}
		$cacheData['data'] = $cache_value;
		$cacheData['lifeTime'] = $lifeTime;
		//以JSON格式写入文件
		if(file_put_contents($this->getCacheFileName($cacheKey), json($cacheData))){
			return true;
		}else{
			$this->tip_message = '写入缓存失败.';
			return false;
		}
    }
	
    /**
     * 修改一个值， 如果已经存在，则改写， 如果不存在则添加
     *
     * @param $cacheKey mixed	键值
     * @param $cache_value mixed 保存的缓存数据
     * @param $lifeTime int	保存时间，如果设置为0则永远不过期， 默认值为0
     * @return boolean 	设置成功为true, 失败为false
     *
     */
    public function set(mixed $cacheKey,mixed $cache_value,int $lifeTime=0): bool
	{
        $cacheData['data'] = $cache_value;
        $cacheData['lifeTime'] = $lifeTime;
        if(file_put_contents($this->getCacheFileName($cacheKey), json($cacheData))){
            return true;
		}else{
            $this->tip_message = '写入缓存失败.';
            return false;
		}
    }
	
	/**
     * 通过指定的键来从缓存文件中获取值
     *
     * @param $cacheKey mixed	键值
     * @return mixed 获取成功返回获取的值，失败返回false
     */
    public function get(string $cacheKey)
	{
		$cacheFileName = $this->getCacheFileName($cacheKey);
        if(!file_exists($cacheFileName)){
			return false;
        }
        $data = $this->objectToArray(json(file_get_contents($cacheFileName), 'de'));
        
        if($this->checkIsvalid($data['lifeTime'])){
            unset($data['lifeTime']);
            return $data['data'];
        }else{
            unlink($cacheFileName);
            $this->tip_message = '数据已过期.';
            return false;
        }  
    }

	/**
     * 通过指定的键删除指定的缓存
     *
     * @param $cacheKey mixed	键值
     * @return boolean，成功返回true, 失败返回false
     */
    public function delete(string $cacheKey): bool
	{
		$cacheFileName = $this->getCacheFileName($cacheKey);    
        if(file_exists($cacheFileName)){
            if(unlink($cacheFileName))
                return true;
            else
                return false;
        }else{
            $this->tip_message = '文件不存在.';
            return true;
        }
    }
	
    /**
     *清除所有缓存文件
     */
    public function flush(): void
	{
        $this->deleteFile($this->cacheDir);
    }

    /**
     *自动清除过期文件
     */
    public function autoDeleteExpiredFile(): void
	{
        $this->deleteFile($this->cacheDir, false);
    }
    
    // |检查目录是否存在,不存在则创建
    private function dirIsvalid(string $dir): bool
	{
        if(is_dir($dir))
            return true;
		
		
        try {
			if(is_dir(dirname($dir)))
				mkdir($dir, 0777);
        }catch (\Exception){
            $this->tip_message = '所设定缓存目录不存在并且创建失败!请检查目录权限!';
            return false;           
        }
		
        return true;
    }
	
    // |检查有效时间
    private function checkIsvalid(int $expired_time = 0): bool
	{
	    if($expired_time == 0) return true;
		if(file_exists($this->cacheFileName)){
			clearstatcache();
			$mtime = filemtime($this->cacheFileName);
			if(gmTime() - $mtime < $expired_time) 
				return true;
		}
		
		return false;
    }
	
    // |获得缓存文件名
    public function getCacheFileName(string $key): string
	{
        $this->cacheFileName = $this->cacheDir. DIRECTORY_SEPARATOR .md5('candy_'.$key). $this->cacheFileSuffix;
        return $this->cacheFileName;
    }
	
    // |object对象转换为数组
    protected function objectToArray($obj): array
	{
        $_arr = is_object($obj) ? get_object_vars($obj) : $obj;
        foreach ($_arr as $key => $val){
            $val = (is_array($val) || is_object($val)) ? $this->objectToArray($val) : $val;
            $arr[$key] = $val;
        }
        return $arr ?: [];
    }
	
    // |删除目录下的所有文件
    // |$mode true删除所有 false删除过期
    protected function deleteFile(string $dir,bool $mode=true): void
	{ 
        $dh = opendir($dir);
		if($dh != false)
			while ($file = readdir($dh)){
				if($file != '.' && $file != '..'){
					$fullpath = $dir . '/' . $file;
					if(!is_dir($fullpath)){
						if($mode){
							unlink($fullpath);
						}else{
							$this->cacheFileName = $fullpath;
							if(!$this->getIsvalidByPath($fullpath))
								unlink($fullpath);
						}
					} else {
						deleteFile($fullpath, $mode);
					}
				}
			}
			closedir($dh);
    }

    //判断文件是否过期
    private function getIsvalidByPath(string $path): bool
	{
        $data = $this->objectToArray(json(file_get_contents($path), 'de'));
        return $this->checkIsvalid($data['lifeTime']);
    }
}
